package com.zp.demo.repush.config;

import com.zp.demo.repush.RePushUtils;
import com.zp.demo.repush.enums.RePushType;
import com.zp.demo.repush.service.DoRePushExcuteService;
import com.zp.demo.repush.service.defined.DefindRePushDbService;
import com.zp.demo.repush.service.defined.DefinedRePushFitter;
import com.zp.demo.repush.vo.BaseRePushVo;
import com.zp.demo.repush.vo.RePushProperties;
import com.zp.demo.repush.vo.RePushStrategyEntity;
import com.zp.demo.repush.vo.ScheduledProperties;
import lombok.Data;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 启动定时任务
 */
@Component
@Data
@Log4j2
public class RePushScheduledRunner implements ApplicationRunner {
    @Autowired
    private RePushProperties rePushProperties;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private ThreadPoolTaskExecutor poolTaskExecutor;
    private Lock lock = new ReentrantLock();
    @Autowired
    private DefindRePushDbService rePushDbService;
    @Autowired
    private DoRePushExcuteService doRePushExcuteService;
    @Autowired
    private DefinedRePushFitter rePushFitter;
    @Autowired
    private ConcurrentMap<String,String> lockMap;
    @Override
    public void run(ApplicationArguments var1) throws Exception{
        if(!rePushProperties.getScheduled().getIsStart()){
            return;
        }
        System.out.println("定时重推任务已经启动 was started!");
        ScheduledProperties scheduledProperties = rePushProperties.getScheduled();
        new Thread(()->{
            while (true){
                List<BaseRePushVo> pushVos = new ArrayList<>();
                // 获取任务列表
                for (Integer i = 0; i < scheduledProperties.getMaxJobs(); i++) {
                    Object obj = redisTemplate.opsForList().leftPop(ScheduledProperties.SCHEDULED_KEY);
                    if(obj != null){
                        BaseRePushVo pushVo = (BaseRePushVo) obj;
                                pushVos.add(pushVo);
                    }
                }
                // 执行
                this.doRun(pushVos);
                try {
                    Thread.sleep(scheduledProperties.getSleepTime());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }

    /**
     * 执行 定时推送
     * @param pushVos
     */
    private void doRun(List<BaseRePushVo> pushVos) {
        //CountDownLatch count = new CountDownLatch(pushVos.size());//计数器
        pushVos.forEach(baseRePushVo ->{
            // 获取策略配置信息
            RePushStrategyEntity baseStrategy = doRePushExcuteService.getStrategyProperties(baseRePushVo.getStrategyKey());
            poolTaskExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    String key = baseRePushVo.getOnlyOnceKey();
                    try {
                    Assert.notNull( baseRePushVo.getRemainingCounts(),"剩余次数不能为空");
                    log.info("---------------------------定时推送【"+baseRePushVo.getMethodId()+"】---------------------------------");
                    int index = baseStrategy.getScheduledCounts() - baseRePushVo.getRemainingCounts()+1;

                    // 加锁判断
                    boolean onlyOnce = baseRePushVo.getOnlyOnce();
                    boolean isFillter = true;
                    // 唯一性判断
                    if(onlyOnce){
                        isFillter = rePushFitter.checkSuccessOnes(key,rePushProperties.getSucessOnceSleepTime());
                    }
                    if(!isFillter){
                        log.info(String.format("[过滤]定时推送：策略:%s ; methodId:%s \n总共需要推送%s次",baseRePushVo.getStrategyKey(),baseRePushVo.getMethodId(),baseStrategy.getScheduledCounts()));
                        log.info(String.format("[重复提交-过滤]定时推送：正在执行第%s次.结果为:【%s】 重复提交-过滤",index,false));
                        rePushDbService.callbackRepush(baseRePushVo,false,baseRePushVo.getRemainingCounts() - 1,RePushType.DS_PUSH,"重复提交-过滤");
                        return;
                    }
                    lock.lock();
                    //加锁
                    if(lockMap.putIfAbsent(key, "1") != null){
                        log.warn(String.format("【定时推送】：策略:%s ; methodId:%s \nKEY【%s正在被抢占】",baseRePushVo.getStrategyKey(),baseRePushVo.getMethodId(),baseRePushVo.getOnlyOnceKey()));
                    }
                    // 重推数据
                    boolean result = doRePushExcuteService.excute(baseRePushVo.getMethodId(),baseRePushVo.getArgs(),baseRePushVo.getIsSucessFunc(),baseRePushVo);
                    log.info(String.format("定时推送：策略:%s ; methodId:%s \n总共需要推送%s次",baseRePushVo.getStrategyKey(),baseRePushVo.getMethodId(),baseStrategy.getScheduledCounts()));
                    log.info(String.format("定时推送：正在执行第%s次.结果为:【%s】",index,result));
                    if(result){
                        log.info(String.format("推送调用方法执行正确。无需再推；共执行了%s次",index));
                        // 清理redis 缓存
                        int backValue = redisTemplate.opsForList().remove(ScheduledProperties.SCHEDULED_KEY, 1, baseRePushVo).intValue();
                        log.info(key + " 移除的数组长度为 : " + backValue);
                    }else{
                        if(baseRePushVo.getOnlyOnce()){
                            rePushFitter.removeCheckSuccess(key);
                        }
                    }
                    // 推送一次的回调操作
                    rePushDbService.callbackRepush(baseRePushVo,result,baseRePushVo.getRemainingCounts()-1,RePushType.DS_PUSH,"执行完成");
                    log.info("---------------------------定时推送【"+baseRePushVo.getMethodId()+"】结束---------------------------------");
                }catch (Exception e){
                        log.error("定时任务报错");
                        e.printStackTrace();
                }finally {
                        lockMap.remove(key);
                        lock.unlock();
                }
                }
            });
        });
    }

}